package cli

import (
	"bytes"
	"encoding/json"
	"reflect"
	"strconv"
	"strings"
	"time"

	"gitee.com/go-errors/errors"
	"github.com/segmentio/go-snakecase"
	"github.com/spf13/pflag"
)

func StructFlag(fs *pflag.FlagSet) *AnyFlag {
	return (*AnyFlag)(fs)
}

type AnyFlag pflag.FlagSet

func (af *AnyFlag) VarP(name, shorthand string, value interface{}, usage string) {
	v, ok := value.(reflect.Value)
	if !ok {
		v = reflect.ValueOf(value)
	}
	v = reflect.Indirect(v)

	if v.Kind() == reflect.Struct {
		af.setStruct(name, value, usage)
		return
	}

	rv := reflectValue(v)
	flag := (*pflag.FlagSet)(af).VarPF(rv, name, shorthand, usage)
	flag.NoOptDefVal = rv.NoOptDefVal()
}

func (af *AnyFlag) setStruct(namePrefix string, structValue interface{}, usagePrefix string) {
	value, ok := structValue.(reflect.Value)
	if !ok {
		value = reflect.ValueOf(structValue)
	}

	if namePrefix != "" {
		namePrefix = namePrefix + "."
	}

	value = reflect.Indirect(value)
	sType := value.Type()
	for i := 0; i < sType.NumField(); i++ {
		tField := sType.Field(i)
		//if flagTag != "" {
		//	flatMap := readTag(flagTag)
		//	if s := flatMap["__0"]; s != "" {
		//		name = namePrefix + s
		//	}
		//	if s := flatMap["__1"]; s != "" {
		//		shorthand = s
		//	}
		//	if s := flatMap["usage"]; s != "" {
		//		usage = usagePrefix + s
		//	}
		//}

		vField := reflect.Indirect(value.Field(i))
		isStruct := vField.Kind() == reflect.Struct
		if isStruct && tField.Anonymous {
			af.setStruct(namePrefix, vField, usagePrefix)
			continue
		}

		name := namePrefix + snakecase.Snakecase(tField.Name)
		if fn := strings.TrimSpace(tField.Tag.Get("flag")); fn != "" {
			if fn == "-" {
				continue
			}
			name = namePrefix + fn
		}
		usage := usagePrefix + strings.TrimSpace(tField.Tag.Get("usage"))

		if isStruct {
			af.setStruct(name, vField, usage)
			continue
		}

		shorthand := tField.Tag.Get("shorthand")
		if len(shorthand) > 0 {
			shorthand = shorthand[:1]
		}
		if isValueType(vField.Kind()) {
			af.VarP(name, shorthand, vField, usage)
		}
	}
}

type reflectValue reflect.Value

func (v reflectValue) Append(s string) error {
	rv := reflect.Indirect(reflect.ValueOf(v))
	if rv.Kind() != reflect.Slice {
		return errors.Errorf("not support: %s", rv.Kind())
	}
	itemKind := rv.Elem().Kind()
	n, err := newValue(itemKind, s)
	if err != nil {
		return errors.Errorf("%w", err)
	}
	rv.Set(reflect.Append(rv, n))
	return nil
}

func (v reflectValue) Replace(vs []string) error {
	rv := reflect.Indirect(reflect.ValueOf(v))
	if rv.Kind() != reflect.Slice {
		return errors.Errorf("not support: %s", rv.Kind())
	}
	itemKind := rv.Elem().Kind()

	nv := rv.Slice(0, 0)
	for _, s := range vs {
		n, err := newValue(itemKind, s)
		if err != nil {
			return errors.Errorf("%w", err)
		}
		nv = reflect.Append(nv, n)
	}
	rv.Set(nv)
	return nil
}

func (v reflectValue) GetSlice() (output []string) {
	rv := reflect.Indirect(reflect.ValueOf(v))
	for i := 0; i < rv.Len(); i++ {
		output = append(output, reflectValue(rv.Index(i)).String())
	}
	return
}

var _ pflag.SliceValue = reflectValue{}

func (v reflectValue) String() string {
	rv := reflect.Indirect(reflect.Value(v))

	o := rv.Interface()
	switch t := o.(type) {
	case time.Time:
		if t.IsZero() {
			return ""
		}
		return t.Format("2006/01/02")
	case time.Duration:
		return t.String()
	}

	switch rv.Kind() {
	case reflect.String:
		return rv.String()
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		return strconv.FormatInt(rv.Int(), 10)
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		return strconv.FormatUint(rv.Uint(), 10)
	case reflect.Float32, reflect.Float64:
		return strconv.FormatFloat(rv.Float(), 'g', -1, 64)
	case reflect.Bool:
		return strconv.FormatBool(rv.Bool())
	case reflect.Slice, reflect.Array:
		var rs = bytes.NewBufferString("[")
		for i := 0; i < rv.Len(); i++ {
			if i > 0 {
				rs.WriteString(", ")
			}
			rs.WriteString(reflectValue(rv.Index(i)).Quote())
		}
		rs.WriteString("]")
		return rs.String()
	default:
		data, _ := json.Marshal(rv.Interface())
		return string(data)
	}
}

func (v reflectValue) Quote() string {
	rv := reflect.Indirect(reflect.Value(v))

	o := rv.Interface()
	switch o.(type) {
	case time.Time:
		return strconv.Quote(rv.String())
	case time.Duration:
		return strconv.Quote(rv.String())
	}

	if rv.Kind() == reflect.String {
		return strconv.Quote(v.String())
	}

	return v.String()
}

func (v reflectValue) Set(s string) error {
	if s == "" {
		return nil
	}

	rv := reflect.Indirect(reflect.Value(v))
	o := rv.Interface()
	switch o.(type) {
	case time.Time:
		layouts := []string{
			"2006-01-02",
			"2006-01-02 15:04",
			"2006-01-02 15:04:05",
			"2006/01/02",
			"2006/01/02 15:04",
			"2006/01/02 15:04:05",
			"2006-01-02T15:04:05Z07:00",
			"20060102",
			"200601021504",
			"20060102150405",
		}
		var (
			pTime time.Time
			err   error
		)

		for _, layout := range layouts {
			if pTime, err = time.Parse(layout, s); err == nil {
				rv.Set(reflect.ValueOf(pTime))
				return nil
			}
		}
		return err
	case time.Duration:
		d, err := time.ParseDuration(s)
		if err == nil {
			rv.SetInt(int64(d))
		}
		return err
	}

	switch rv.Kind() {
	case reflect.String:
		rv.SetString(s)
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		v, err := strconv.ParseInt(s, 0, 0)
		if err != nil {
			return err
		}
		rv.SetInt(v)
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		v, err := strconv.ParseUint(s, 0, 0)
		if err != nil {
			return err
		}
		rv.SetUint(v)
	case reflect.Float32:
		v, err := strconv.ParseFloat(s, 32)
		if err != nil {
			return err
		}
		rv.SetFloat(v)
	case reflect.Float64:
		v, err := strconv.ParseFloat(s, 64)
		if err != nil {
			return err
		}
		rv.SetFloat(v)
	case reflect.Bool:
		v, err := strconv.ParseBool(s)
		if err != nil {
			return err
		}
		rv.SetBool(v)
	case reflect.Array:

	case reflect.Slice:
	default:
		if rv.CanAddr() {
			return json.Unmarshal([]byte(s), rv.Addr().Interface())
		}
		return errors.Errorf("not support: %s", rv.Kind())
	}
	return nil
}

func (v reflectValue) NoOptDefVal() string {
	if reflect.Indirect(reflect.Value(v)).Kind() == reflect.Bool {
		return "true"
	}
	return ""
}

func (v reflectValue) Type() string {
	rv := reflect.Indirect(reflect.Value(v))
	switch rv.Kind() {
	case reflect.String:
		return "string"
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
		reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		return "int"
	case reflect.Float32, reflect.Float64:
		return "float"
	case reflect.Bool:
		return "boolean"
	default:
		return rv.Kind().String()
	}
}

func isValueType(vType reflect.Kind) bool {
	valueKinds := []reflect.Kind{
		reflect.Int,
		reflect.Int8,
		reflect.Int16,
		reflect.Int32,
		reflect.Int64,
		reflect.Uint,
		reflect.Uint8,
		reflect.Uint16,
		reflect.Uint32,
		reflect.Uint64,
		reflect.Float32,
		reflect.Float64,
		reflect.Bool,
		reflect.String,
	}
	for _, kind := range valueKinds {
		if vType == kind {
			return true
		}
	}
	return false
}

func newValue(kind reflect.Kind, s string) (reflect.Value, error) {
	switch kind {
	case reflect.String:
		return reflect.ValueOf(s), nil
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		v, err := strconv.ParseInt(s, 0, 0)
		if err != nil {
			return reflect.Value{}, err
		}
		return reflect.ValueOf(v), nil
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		v, err := strconv.ParseUint(s, 0, 0)
		if err != nil {
			return reflect.Value{}, err
		}
		return reflect.ValueOf(v), nil
	case reflect.Float32:
		v, err := strconv.ParseFloat(s, 32)
		if err != nil {
			return reflect.Value{}, err
		}
		return reflect.ValueOf(v), nil
	case reflect.Float64:
		v, err := strconv.ParseFloat(s, 64)
		if err != nil {
			return reflect.Value{}, err
		}
		return reflect.ValueOf(v), nil
	case reflect.Bool:
		v, err := strconv.ParseBool(s)
		if err != nil {
			return reflect.Value{}, err
		}
		return reflect.ValueOf(v), nil
	default:
		return reflect.Value{}, errors.Errorf("not support %s", kind)
	}
}

//reflect.Int
//reflect.Int8
//reflect.Int16
//reflect.Int32
//reflect.Int64
//reflect.Uint
//reflect.Uint8
//reflect.Uint16
//reflect.Uint32
//reflect.Uint64
//reflect.Float32
//reflect.Float64
//reflect.Bool
//reflect.String

//switch rv.Kind() {
//case reflect.String:
//case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
//case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
//case reflect.Float32, reflect.Float64:
//case reflect.Bool:
//}
//
//func readTag(tag string) map[string]string {
//	result := map[string]string{}
//	if tag != "" {
//		repl := strings.NewReplacer(
//			`\"`, "_!_sy_!_",
//			`\'`, "_!_dy_!_",
//			`\:`, "_!_m_!_",
//			`\;`, "_!_f_!_",
//		)
//		rRepl := strings.NewReplacer(
//			"_!_sy_!_", `\"`,
//			"_!_dy_!_", `\'`,
//			"_!_m_!_", `\:`,
//			"_!_f_!_", `\;`,
//		)
//		tag = repl.Replace(tag)
//		tagItems := strings.Split(tag, ";")
//		narg := 0
//		for _, item := range tagItems {
//			mIdx := strings.Index(item, ":")
//			if mIdx == -1 {
//				result["__"+strconv.Itoa(narg)] = strings.TrimSpace(rRepl.Replace(item))
//				narg++
//			}
//			if mIdx == 0 {
//				continue
//			}
//			if mIdx > 0 {
//				key := strings.TrimSpace(rRepl.Replace(item[:mIdx]))
//				value := strings.TrimSpace(rRepl.Replace(item[mIdx+1:]))
//				result[key] = value
//			}
//		}
//	}
//	return result
//}
